/*
 * Copyright (c) 2023 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "sec_storage_core_inner.h"

#include <securec.h>
#include <stddef.h>

#include "apdu_core_defines.h"
#include "apdu_utils.h"
#include "card_channel.h"
#include "logger.h"
#include "parcel.h"

#define PREPARE_RESET_RSP_LEN 0x40

ResultCode StorageResponseChecker(const ResponseApdu *apdu)
{
    return DefaultResponseChecker(apdu);
}

ResultCode StorageGetFactoryResetAuthenticationAlgoFromRspApdu(const ResponseApdu *apdu, FactoryResetAuthAlgo *algo)
{
    if (apdu == NULL || algo == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }
    uint8_t output = 0;
    if (!GetResponseUint8ValueAt(apdu, 0, &output)) {
        LOG_ERROR("GetResponseUint8ValueAt algo failed");
        return RES_APDU_DATA_ERR_INDEX_1;
    }
    *algo = (FactoryResetAuthAlgo)output;
    return SUCCESS;
}

ResultCode StoragePrepareFactoryResetFromRspApdu(const ResponseApdu *apdu, uint8_t *nonce, uint32_t *length)
{
    if (apdu == NULL || nonce == NULL || length == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (*length < PREPARE_RESET_RSP_LEN) {
        return INVALID_PARA_ERR_SIZE;
    }

    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }

    if (!GetResponseBufferValueAt(apdu, 0, nonce, PREPARE_RESET_RSP_LEN)) {
        LOG_ERROR("GetResponseBufferValueAt nonce failed");
        return RES_APDU_DATA_ERR_INDEX_1;
    }

    *length = PREPARE_RESET_RSP_LEN;
    return SUCCESS;
}

ResultCode StorageGetSupportedAlgorithmsFromRspApdu(const ResponseApdu *apdu, uint8_t *resetAlgo, uint8_t *operAlgo)
{
    if (apdu == NULL || resetAlgo == NULL || operAlgo == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }

    if (!GetResponseUint8ValueAt(apdu, 0, resetAlgo)) {
        LOG_ERROR("GetResponseBufferValueAt nonce failed");
        return RES_APDU_DATA_ERR_INDEX_1;
    }

    if (!GetResponseUint8ValueAt(apdu, 1, operAlgo)) {
        LOG_ERROR("GetResponseBufferValueAt nonce failed");
        return RES_APDU_DATA_ERR_INDEX_2;
    }

    return SUCCESS;
}

ResultCode StorageGetAllSlotsSizeFromRspApdu(const ResponseApdu *apdu, uint16_t *slotSizeArray, uint32_t *arrayLength)
{
    if (apdu == NULL || slotSizeArray == NULL || arrayLength == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }

    uint32_t length = 0;
    const uint8_t *data = GetResponseDataBuffer(apdu, &length);
    if (data == NULL) {
        LOG_ERROR("GetResponseDataBuffer error");
        return RES_APDU_DATA_ERR;
    }

    if (length == 0 || length % 2 != 0) { // the len must to be divisible by 2
        LOG_ERROR("GetResponseDataBuffer error, size is %u", length);
        return RES_APDU_FMT_ERR;
    }
    uint8_t *dest = (uint8_t *)slotSizeArray;
    uint32_t destSize = (*arrayLength) * sizeof(uint16_t);

    if (memcpy_s(dest, destSize, data, length) != EOK) {
        LOG_ERROR("memcpy_s error, src size is %u, dst size is %u", length, destSize);
        return RES_APDU_FMT_ERR;
    }

    *arrayLength = (length >> 1);
    for (uint32_t loop = 0; loop < *arrayLength; loop++) {
        DataRevert((uint8_t *)&slotSizeArray[loop], sizeof(uint16_t));
    }

    return SUCCESS;
}

ResultCode StorageGetSlotStatusFromRspApdu(const ResponseApdu *apdu, StorageSlotStatus *status)
{
    if (apdu == NULL || status == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }

    uint32_t length = 0;
    const uint8_t *data = GetResponseDataBuffer(apdu, &length);
    if (data == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    // readcnt(4) + writecnt(4) + freecnt(4) + status(1) + size(2) + flag (1) = 16
    if (length != GET_SLOT_STATUS_RSP_APDU_DATA_LEN) {
        LOG_ERROR("rsp size is not right, value is %u", length);
        return RES_APDU_FMT_ERR;
    }
    (void)memset_s(status, sizeof(StorageSlotStatus), 0, sizeof(StorageSlotStatus));

    uint32_t index = 0;

    if (!GetResponseUint32ValueAt(apdu, index, &status->readErrCnt)) {
        return RES_APDU_DATA_ERR_INDEX_1;
    }
    index += sizeof(uint32_t);

    if (!GetResponseUint32ValueAt(apdu, index, &status->writeErrCnt)) {
        return RES_APDU_DATA_ERR_INDEX_2;
    }
    index += sizeof(uint32_t);

    if (!GetResponseUint32ValueAt(apdu, index, &status->freeErrCnt)) {
        return RES_APDU_DATA_ERR_INDEX_3;
    }
    index += sizeof(uint32_t);

    uint8_t allocStatus = 0;
    if (!GetResponseUint8ValueAt(apdu, index, &allocStatus)) {
        return RES_APDU_DATA_ERR_INDEX_4;
    }
    status->status = allocStatus;

    index += sizeof(uint8_t);

    uint16_t size = 0;
    if (!GetResponseUint16ValueAt(apdu, index, &size)) {
        return RES_APDU_DATA_ERR_INDEX_5;
    }
    index += sizeof(uint16_t);
    status->slotAttr.size = size;

    uint8_t flag = 0;
    if (!GetResponseUint8ValueAt(apdu, index, &flag)) {
        return RES_APDU_DATA_ERR_INDEX_6;
    }
    return StorageGetSlotStatusParseFlag(flag, &status->slotAttr);
}

ResultCode StorageGetSlotStatusParseFlag(uint8_t flag, StorageSlotAttr *attr)
{
    if (attr == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (flag & BIT_VALUE_FACTORY_WIPE_BIT) {
        attr->resetLevel = LEVEL_FACTORY_WIPE;
    }

    if (flag & BIT_VALUE_AUTH_ON_FREE) {
        attr->authOnFree = 1;
    }

    if (flag & BIT_VALUE_AUTH_ON_WRITE) {
        attr->authOnWrite = 1;
    }

    if (flag & BIT_VALUE_AUTH_ON_READ) {
        attr->authOnRead = 1;
    }

    if ((flag & BIT_VALUE_ALGO_MASK) == BIT_VALUE_ALGO_HMAC_SHA256) {
        attr->algo = ALGO_HKDF_SHA256;
    }

    if ((flag & BIT_VALUE_ALGO_MASK) == BIT_VALUE_ALGO_HMAC_SM3) {
        attr->algo = ALGO_HKDF_SM3;
    }

    if ((flag & BIT_VALUE_ALGO_MASK) == BIT_VALUE_ALGO_RAW) {
        attr->algo = ALGO_RAW_KEY;
    }

    return SUCCESS;
}

ResultCode ConvertStorageSlotAttrToBitMap(const StorageSlotAttr *attr, uint8_t *flag)
{
    if (attr == NULL || flag == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (attr->algo > ALGO_HKDF_SM3) {
        return INVALID_PARA_ERR_CMD;
    }

    uint8_t out = 0;

    if (attr->resetLevel == LEVEL_FACTORY_WIPE) {
        out |= BIT_VALUE_FACTORY_WIPE_BIT;
    }
    if (attr->authOnFree) {
        out |= BIT_VALUE_AUTH_ON_FREE;
    }
    if (attr->authOnWrite) {
        out |= BIT_VALUE_AUTH_ON_WRITE;
    }
    if (attr->authOnRead) {
        out |= BIT_VALUE_AUTH_ON_READ;
    }

    if (attr->algo == ALGO_HKDF_SHA256) {
        out |= BIT_VALUE_ALGO_HMAC_SHA256;
    } else if (attr->algo == ALGO_HKDF_SM3) {
        out |= BIT_VALUE_ALGO_HMAC_SM3;
    } else {
        out |= BIT_VALUE_ALGO_RAW;
    }
    *flag = out;
    return SUCCESS;
}

ResultCode StorageWriteSlotToCmdApduData(const StorageAuthKey *key, const StorageDataArea *area,
    const StorageDataBuffer *data, uint8_t *body, uint32_t *size)
{
    if (key == NULL || area == NULL || data == NULL) {
        LOG_ERROR("input key or area or data is nullptr");
        return INVALID_PARA_NULL_PTR;
    }

    if (body == NULL || size == NULL) {
        LOG_ERROR("input body or size nullptr");
        return INVALID_PARA_NULL_PTR;
    }

    if (area->length != data->bufferSize) {
        LOG_ERROR("input data size is not same, %u != %u", area->length, data->bufferSize);
        return INVALID_PARA_ERR_SIZE;
    }

    ResultCode ret = MEM_COPY_ERR;
    Parcel parcel = CreateParcel(*size);
    do {
        if (!ParcelWrite(&parcel, key->keyData, key->keySize)) {
            break;
        }
        uint16_t offset = area->offset;
        DataRevert((uint8_t *)&offset, sizeof(uint16_t));
        if (!ParcelWriteUint16(&parcel, offset)) {
            break;
        }

        uint16_t length = area->length;
        DataRevert((uint8_t *)&length, sizeof(uint16_t));
        if (!ParcelWriteUint16(&parcel, length)) {
            break;
        }
        if (!ParcelWrite(&parcel, data->bufferData, data->bufferSize)) {
            break;
        }

        if (!ParcelToDataBuffer(&parcel, body, size)) {
            break;
        }
        ret = SUCCESS;
    } while (0);

    DeleteParcel(&parcel);
    return ret;
}

ResultCode StorageReadSlotToCmdApduData(const StorageAuthKey *key, const StorageDataArea *area, uint8_t *body,
    uint32_t *size)
{
    if (key == NULL || area == NULL) {
        LOG_ERROR("input key or area or data is nullptr");
        return INVALID_PARA_NULL_PTR;
    }

    if (body == NULL || size == NULL) {
        LOG_ERROR("input body or size nullptr");
        return INVALID_PARA_NULL_PTR;
    }

    ResultCode ret = MEM_COPY_ERR;
    Parcel parcel = CreateParcel(*size);
    do {
        if (!ParcelWrite(&parcel, key->keyData, key->keySize)) {
            break;
        }
        uint16_t offset = area->offset;
        DataRevert((uint8_t *)&offset, sizeof(uint16_t));
        if (!ParcelWriteUint16(&parcel, offset)) {
            break;
        }

        uint16_t length = area->length;
        DataRevert((uint8_t *)&length, sizeof(uint16_t));
        if (!ParcelWriteUint16(&parcel, length)) {
            break;
        }

        if (!ParcelToDataBuffer(&parcel, body, size)) {
            break;
        }
        ret = SUCCESS;
    } while (0);

    DeleteParcel(&parcel);
    return ret;
}

ResultCode StorageReadSlotFromRspApdu(const ResponseApdu *apdu, StorageDataBuffer *data)
{
    if (apdu == NULL || data == NULL) {
        return INVALID_PARA_NULL_PTR;
    }

    if (!CheckStatusWords(apdu, SW_NO_ERROR)) {
        LOG_ERROR("CheckStatusWords failed");
        return RES_APDU_SW_ERR;
    }

    uint32_t length = 0;
    const uint8_t *rsp = GetResponseDataBuffer(apdu, &length);
    if (rsp == NULL) {
        LOG_ERROR("GetResponseDataBuffer failed");
        return RES_APDU_LENGTH_ERR;
    }

    if (memcpy_s(data->bufferData, data->bufferSize, rsp, length) != EOK) {
        LOG_ERROR("GetResponseDataBuffer failed, actual rsp size is %u", length);
        return RES_APDU_DATA_ERR;
    }
    data->bufferSize = length;
    return SUCCESS;
}